
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 *版权所有2002-2023原作者。
 *
 *根据Apache许可证2.0版许可（“许可证”）；
 *除非符合许可证的规定，否则您不得使用此文件。
 *您可以在以下网址获取许可证副本：
 *
 *https://www.apache.org/licenses/LICENSE-2.0
 *
 *除非适用法律要求或书面同意，软件
 *根据许可证进行的分发是在“按原样”的基础上进行的，
 *无任何明示或暗示的保证或条件。
 *有关管理权限的特定语言，请参阅许可证和
 *许可证下的限制。
 */

package org.springframework.scheduling.support;

import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.Arrays;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Representation of a
 * <a href="https://www.manpagez.com/man/5/crontab/">crontab expression</a>
 * that can calculate the next time it matches.
 *
 * <p>{@code CronExpression} instances are created through
 * {@link #parse(String)}; the next match is determined with
 * {@link #next(Temporal)}.
 *
 * @author Arjen Poutsma
 * @since 5.3
 * @see CronTrigger
 */
/**
 *代表
 *<a href=“https://www.manpagez.com/man/5/crontab/“>crontab表达式</a>
 *可以计算下一次匹配的时间。
 *
 *＜p＞｛@code CronExpression｝实例是通过
 *｛@link#parse（字符串）｝；下一个匹配由
 *｛@link#next（临时）｝。
 *
 *@作者Arjen Poutsma
 *@自5.3起
 *@见CronTrigger
 */
public final class CronExpression {

	static final int MAX_ATTEMPTS = 366;

	private static final String[] MACROS = new String[] {
			"@yearly", "0 0 0 1 1 *",
			"@annually", "0 0 0 1 1 *",
			"@monthly", "0 0 0 1 * *",
			"@weekly", "0 0 0 * * 0",
			"@daily", "0 0 0 * * *",
			"@midnight", "0 0 0 * * *",
			"@hourly", "0 0 * * * *"
	};


	private final CronField[] fields;

	private final String expression;


	private CronExpression(
			CronField seconds,
			CronField minutes,
			CronField hours,
			CronField daysOfMonth,
			CronField months,
			CronField daysOfWeek,
			String expression) {

		// reverse order, to make big changes first
		// 颠倒顺序，先做大的改变
		// to make sure we end up at 0 nanos, we add an extra field
		// 为了确保我们最终达到0纳米，我们添加了一个额外的字段
		this.fields = new CronField[]{daysOfWeek, months, daysOfMonth, hours, minutes, seconds, CronField.zeroNanos()};
		this.expression = expression;
	}


	/**
	 * Parse the given
	 * <a href="https://www.manpagez.com/man/5/crontab/">crontab expression</a>
	 * string into a {@code CronExpression}.
	 * The string has six single space-separated time and date fields:
	 * <pre>
	 * &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; second (0-59)
	 * &#9474; &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; minute (0 - 59)
	 * &#9474; &#9474; &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; hour (0 - 23)
	 * &#9474; &#9474; &#9474; &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; day of the month (1 - 31)
	 * &#9474; &#9474; &#9474; &#9474; &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; month (1 - 12) (or JAN-DEC)
	 * &#9474; &#9474; &#9474; &#9474; &#9474; &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472; day of the week (0 - 7)
	 * &#9474; &#9474; &#9474; &#9474; &#9474; &#9474;          (0 or 7 is Sunday, or MON-SUN)
	 * &#9474; &#9474; &#9474; &#9474; &#9474; &#9474;
	 * &#42; &#42; &#42; &#42; &#42; &#42;
	 * </pre>
	 *
	 * <p>The following rules apply:
	 * <ul>
	 * <li>
	 * A field may be an asterisk ({@code *}), which always stands for
	 * "first-last". For the "day of the month" or "day of the week" fields, a
	 * question mark ({@code ?}) may be used instead of an asterisk.
	 * </li>
	 * <li>
	 * Ranges of numbers are expressed by two numbers separated with a hyphen
	 * ({@code -}). The specified range is inclusive.
	 * </li>
	 * <li>Following a range (or {@code *}) with {@code /n} specifies
	 * the interval of the number's value through the range.
	 * </li>
	 * <li>
	 * English names can also be used for the "month" and "day of week" fields.
	 * Use the first three letters of the particular day or month (case does not
	 * matter).
	 * </li>
	 * <li>
	 * The "day of month" and "day of week" fields can contain a
	 * {@code L}-character, which stands for "last", and has a different meaning
	 * in each field:
	 * <ul>
	 * <li>
	 * In the "day of month" field, {@code L} stands for "the last day of the
	 * month". If followed by an negative offset (i.e. {@code L-n}), it means
	 * "{@code n}th-to-last day of the month". If followed by {@code W} (i.e.
	 * {@code LW}), it means "the last weekday of the month".
	 * </li>
	 * <li>
	 * In the "day of week" field, {@code dL} or {@code DDDL} stands for
	 * "the last day of week {@code d} (or {@code DDD}) in the month".
	 * </li>
	 * </ul>
	 * </li>
	 * <li>
	 * The "day of month" field can be {@code nW}, which stands for "the nearest
	 * weekday to day of the month {@code n}".
	 * If {@code n} falls on Saturday, this yields the Friday before it.
	 * If {@code n} falls on Sunday, this yields the Monday after,
	 * which also happens if {@code n} is {@code 1} and falls on a Saturday
	 * (i.e. {@code 1W} stands for "the first weekday of the month").
	 * </li>
	 * <li>
	 * The "day of week" field can be {@code d#n} (or {@code DDD#n}), which
	 * stands for "the {@code n}-th day of week {@code d} (or {@code DDD}) in
	 * the month".
	 * </li>
	 * </ul>
	 *
	 * <p>Example expressions:
	 * <ul>
	 * <li>{@code "0 0 * * * *"} = the top of every hour of every day.</li>
	 * <li><code>"*&#47;10 * * * * *"</code> = every ten seconds.</li>
	 * <li>{@code "0 0 8-10 * * *"} = 8, 9 and 10 o'clock of every day.</li>
	 * <li>{@code "0 0 6,19 * * *"} = 6:00 AM and 7:00 PM every day.</li>
	 * <li>{@code "0 0/30 8-10 * * *"} = 8:00, 8:30, 9:00, 9:30, 10:00 and 10:30 every day.</li>
	 * <li>{@code "0 0 9-17 * * MON-FRI"} = on the hour nine-to-five weekdays</li>
	 * <li>{@code "0 0 0 25 12 ?"} = every Christmas Day at midnight</li>
	 * <li>{@code "0 0 0 L * *"} = last day of the month at midnight</li>
	 * <li>{@code "0 0 0 L-3 * *"} = third-to-last day of the month at midnight</li>
	 * <li>{@code "0 0 0 1W * *"} = first weekday of the month at midnight</li>
	 * <li>{@code "0 0 0 LW * *"} = last weekday of the month at midnight</li>
	 * <li>{@code "0 0 0 * * 5L"} = last Friday of the month at midnight</li>
	 * <li>{@code "0 0 0 * * THUL"} = last Thursday of the month at midnight</li>
	 * <li>{@code "0 0 0 ? * 5#2"} = the second Friday in the month at midnight</li>
	 * <li>{@code "0 0 0 ? * MON#1"} = the first Monday in the month at midnight</li>
	 * </ul>
	 *
	 * <p>The following macros are also supported:
	 * <ul>
	 * <li>{@code "@yearly"} (or {@code "@annually"}) to run un once a year, i.e. {@code "0 0 0 1 1 *"},</li>
	 * <li>{@code "@monthly"} to run once a month, i.e. {@code "0 0 0 1 * *"},</li>
	 * <li>{@code "@weekly"} to run once a week, i.e. {@code "0 0 0 * * 0"},</li>
	 * <li>{@code "@daily"} (or {@code "@midnight"}) to run once a day, i.e. {@code "0 0 0 * * *"},</li>
	 * <li>{@code "@hourly"} to run once an hour, i.e. {@code "0 0 * * * *"}.</li>
	 * </ul>
	 * @param expression the expression string to parse
	 * @return the parsed {@code CronExpression} object
	 * @throws IllegalArgumentException in the expression does not conform to
	 * the cron format
	 */
	/**
	 *分析给定的
	 *<a href=“https://www.manpagez.com/man/5/crontab/“>crontab表达式</a>
	 *字符串转换为{@code CronExpression}。
	 *该字符串有六个单空格分隔的时间和日期字段：
	 *<pre>
	 *&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；第二（0-59）
	 *&#9474&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；分钟（0-59）
	 *&#9474&#9474页&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；小时（0-23）
	 *&#9474&#9474页&#9474页&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；一个月的哪一天（1-31）
	 *&#9474&#9474页&#9474页&#9474页&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；月（1-12）（或JAN-DEC）
	 *&#9474&#9474页&#9474页&#9474页&#9474页&#9484&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页&#9472页；星期几（0-7）
	 *&#9474&#9474页&#9474页&#9474页&#9474页&#9474页；（0或7是星期日，或MON-SUN）
	 *&#9474&#9474页&#9474页&#9474页&#9474页&#9474页；
	 *&#42&#42&#42&#42&#42&#42；
	 *</pre>
	 *
	 *<p>以下规则适用：
	 *<ul>
	 *<li>
	 *字段可以是星号（{@code*}），它始终代表
	 *“倒数第一”。对于“月日”或“周日”字段
	 *可以使用问号（{@code？}）而不是星号。
	 *</li>
	 *<li>
	 *数字范围由两个用连字符分隔的数字表示
	 *（｛@code-｝）。指定的范围包括在内。
	 *</li>
	 *＜li＞在范围（或｛@code*｝）后加｛@code/n｝指定
	 *数值在整个范围内的间隔。
	 *</li>
	 *<li>
	 *英文名称也可以用于“month”和“day of week”字段。
	 *使用特定日期或月份的前三个字母（大小写不
	 *物质）。
	 *</li>
	 *<li>
	 *“月日”和“周日”字段可以包含
	 *{@code L}-字符，代表“最后”，有不同的含义
	 *在每个字段中：
	 *<ul>
	 *<li>
	 *在“月的最后一天”字段中，{@code L}代表“
	 *如果后面跟一个负偏移量（即{@code L-n}），则表示
	 *“｛@code n｝th到该月的最后一天”。如果后面跟着{@code W}（即。
	 *{@codeLW}），意思是“一个月的最后一个工作日”。
	 *</li>
	 *<li>
	 *在“星期几”字段中，{@code dL}或{@code DDDL}代表
	 *“本月第{@code d}周（或{@code DDD}）的最后一天”。
	 *</li>
	 *</ul>
	 *</li>
	 *<li>
	 *“月的哪一天”字段可以是{@codenW}，代表“最近的
	 *从星期日到一个月的第{@coden}天”。
	 *如果{@coden}落在星期六，这将产生前一个星期五。
	 *如果{@coden}落在星期天，则这将产生下一个星期一，
	 *如果{@coden}是{@code1}并且正好在星期六，也会发生这种情况
	 *（即{@code1W}代表“一个月的第一个工作日”）。
	 *</li>
	 *<li>
	 *“星期几”字段可以是{@code d#n}（或{@code DDD#n}）
	 *代表“一周中的第{@code n}天{@code d}（或{@code DDD}）
	 *月”。
	 *</li>
	 *</ul>
	 *
	 *<p>示例表达式：
	 *<ul>
	 *<li>｛@code“0 0***”｝=每天每小时的顶部</li>
	 *<li><code>“**#47；10***”</code>=每10秒一次</li>
	 *<li>｛@code“0 0 8-10***”｝=每天8点、9点和10点</li>
	 *<li>｛@code“0 0 6,19***”｝=每天上午6点和下午7点</li>
	 *<li>｛@code“0 0/30 8-10***”｝=每天8:00、8:30、9:00、9:30、10:00和10:30</li>
	 *<li>｛@code“0 0 9-17**MON-FRI”｝=工作日朝九晚五</li>
	 *<li>｛@code“0 0 25 12？”｝=每个圣诞节的午夜</li>
	 *<li>｛@code“0 0 L**”｝=本月最后一天午夜</li>
	 *<li>｛@code“0 0 L-3**”｝=每月第三天至最后一天午夜</li>
	 *<li>｛@code“0 0 1W**”｝=本月第一个工作日午夜</li>
	 *<li>｛@code“0 0 LW**”｝=本月最后一个工作日午夜</li>
	 *<li>｛@code“0 0**5L”｝=本月最后一个星期五午夜</li>
	 *<li>｛@code“0 0**THUL”｝=本月最后一个星期四午夜</li>
	 *<li>｛@code“0 0？*5#2”｝=该月的第二个星期五午夜</li>
	 *<li>｛@code“0 0 0？*MON#1”｝=该月的第一个星期一午夜</li>
	 *</ul>
	 *
	 *<p>还支持以下宏：
	 *<ul>
	 *<li>｛@code“@yearly”｝（或｛@code“@pennial”｝）每年运行一次，即｛@ccode“0 0 1 1*”｝</li>
	 *<li>｛@code“@monthly”｝每月运行一次，即｛@ccode“0 0 0 1**”｝</li>
	 *<li>｛@code“@weekly”｝每周运行一次，即｛@ccode“0 0**0”｝</li>
	 *<li>｛@code“@daily”｝（或｛@code“@snight”｝）每天运行一次，即｛@ccode“0 0***”｝</li>
	 *<li>｛@code“@hourly”｝每小时运行一次，即｛@ccode“0 0***”｝</li>
	 *</ul>
	 *@param expression要解析的表达式字符串
	 *@返回解析后的｛@code CronExpression｝对象
	 *@throws IllegalArgumentException在表达式中不符合
	 *cron格式
	 */
	public static CronExpression parse(String expression) {
		Assert.hasLength(expression, "Expression string must not be empty");

		expression = resolveMacros(expression);

		String[] fields = StringUtils.tokenizeToStringArray(expression, " ");
		if (fields.length != 6) {
			throw new IllegalArgumentException(String.format(
					"Cron expression must consist of 6 fields (found %d in \"%s\")", fields.length, expression));
		}
		try {
			CronField seconds = CronField.parseSeconds(fields[0]);
			CronField minutes = CronField.parseMinutes(fields[1]);
			CronField hours = CronField.parseHours(fields[2]);
			CronField daysOfMonth = CronField.parseDaysOfMonth(fields[3]);
			CronField months = CronField.parseMonth(fields[4]);
			CronField daysOfWeek = CronField.parseDaysOfWeek(fields[5]);

			return new CronExpression(seconds, minutes, hours, daysOfMonth, months, daysOfWeek, expression);
		}
		catch (IllegalArgumentException ex) {
			String msg = ex.getMessage() + " in cron expression \"" + expression + "\"";
			throw new IllegalArgumentException(msg, ex);
		}
	}

	/**
	 * Determine whether the given string represents a valid cron expression.
	 * @param expression the expression to evaluate
	 * @return {@code true} if the given expression is a valid cron expression
	 * @since 5.3.8
	 */
	/**
	 *确定给定的字符串是否表示有效的cron表达式。
	 *@param expression要计算的表达式
	 *@return｛@code true｝如果给定的表达式是有效的cron表达式
	 *@自5.3.8起
	 */
	public static boolean isValidExpression(@Nullable String expression) {
		if (expression == null) {
			return false;
		}
		try {
			parse(expression);
			return true;
		}
		catch (IllegalArgumentException ex) {
			return false;
		}
	}


	private static String resolveMacros(String expression) {
		expression = expression.trim();
		for (int i = 0; i < MACROS.length; i = i + 2) {
			if (MACROS[i].equalsIgnoreCase(expression)) {
				return MACROS[i + 1];
			}
		}
		return expression;
	}


	/**
	 * Calculate the next {@link Temporal} that matches this expression.
	 * @param temporal the seed value
	 * @param <T> the type of temporal
	 * @return the next temporal that matches this expression, or {@code null}
	 * if no such temporal can be found
	 */
	/**
	 *计算下一个与此表达式匹配的{@link-Temporal}。
	 *@param temporal种子值
	 *@param＜T＞时态的类型
	 *@返回与此表达式匹配的下一个时态，或｛@code null｝
	 *如果找不到这样的时态
	 */
	@Nullable
	public <T extends Temporal & Comparable<? super T>> T next(T temporal) {
		return nextOrSame(ChronoUnit.NANOS.addTo(temporal, 1));
	}


	@Nullable
	private <T extends Temporal & Comparable<? super T>> T nextOrSame(T temporal) {
		for (int i = 0; i < MAX_ATTEMPTS; i++) {
			T result = nextOrSameInternal(temporal);
			if (result == null || result.equals(temporal)) {
				return result;
			}
			temporal = result;
		}
		return null;
	}

	@Nullable
	private <T extends Temporal & Comparable<? super T>> T nextOrSameInternal(T temporal) {
		for (CronField field : this.fields) {
			temporal = field.nextOrSame(temporal);
			if (temporal == null) {
				return null;
			}
		}
		return temporal;
	}


	@Override
	public int hashCode() {
		return Arrays.hashCode(this.fields);
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o instanceof CronExpression other) {
			return Arrays.equals(this.fields, other.fields);
		}
		else {
			return false;
		}
	}

	/**
	 * Return the expression string used to create this {@code CronExpression}.
	 * @return the expression string
	 */
	/**
	 *返回用于创建此｛@code CronExpression｝的表达式字符串。
	 *@返回表达式字符串
	 */
	@Override
	public String toString() {
		return this.expression;
	}

}
